package com.mobimagic.lockscreen.view.material;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.widget.AdapterView;

import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorListenerAdapter;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.util.Property;

public class MaterialRippleHolder {

    private View targetView;

    private static final int DEFAULT_DURATION = 150;
    private static final int DEFAULT_FADE_DURATION = 50;
    private static final float DEFAULT_DIAMETER_DP = 35;
    private static final float DEFAULT_ALPHA = 0.1f;
    private static final int DEFAULT_COLOR = Color.BLACK;
    private static final int DEFAULT_BACKGROUND = Color.TRANSPARENT;
    private static final boolean DEFAULT_HOVER = true;
    private static final boolean DEFAULT_DELAY_CLICK = true;
    private static final boolean DEFAULT_PERSISTENT = false;
    private static final boolean DEFAULT_SEARCH_ADAPTER = false;
    private static final boolean DEFAULT_RIPPLE_OVERLAY = true;
    private static final boolean DEFAULT_INTERCEPT_TOUCH_EVENT = false;
    private static final boolean DEFAULT_SUPER_TOUCH_EVENT = false;
    private static final boolean DEFAULT_AVOID_TWICE_TOUCH = false;

    private static final int FADE_EXTRA_DELAY = 50;
    private static final long HOVER_DURATION = 500;

    private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private final Rect bounds = new Rect();

    private int rippleColor;
    private boolean rippleOverlay;
    private boolean rippleHover;
    private int rippleDiameter;
    private int rippleDuration;
    private int rippleAlpha;
    private boolean rippleDelayClick;
    private int rippleFadeDuration;
    private boolean ripplePersistent;
    private Drawable rippleBackground;
    @Deprecated
    private boolean rippleInAdapter;

    private boolean mInterceptTouchEvent;
    private boolean mSuperTouchEvent;

    /**
     * Don't know why click perform twice yet, just workaround and fix later
     */
    private boolean mAvoidTwiceTouch;

    private float radius;

    private AdapterView parentAdapter;

    private AnimatorSet rippleAnimator;
    private ObjectAnimator hoverAnimator;

    private final Point currentCoords = new Point();
    private Point previousCoords = new Point();

    private boolean eventCancelled;
    private boolean prepressed;
    private int positionInAdapter;

    private PressedEvent pendingPressEvent;

    private Context context;
    private OnClickListener onClickListener;

    public OnClickListener getOnClickListener() {
        return onClickListener;
    }

    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    public static RippleBuilder on(View view) {
        return new RippleBuilder(view);
    }

    private Resources getResources() {
        return context.getResources();
    }

    private Context getContext() {
        return context;
    }

    public void init(Context context, AttributeSet attrs, int defStyle, View targetView) {
        this.context = context;
        this.targetView = targetView;
        this.targetView.setWillNotDraw(false);
        //        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MaterialRippleLayout);
        //        rippleColor = a.getColor(R.styleable.MaterialRippleLayout_rippleColor, DEFAULT_COLOR);
        //        rippleDiameter = a.getDimensionPixelSize(R.styleable.MaterialRippleLayout_rippleDimension,
        //                (int) dpToPx(getResources(), DEFAULT_DIAMETER_DP));
        //        rippleOverlay = a.getBoolean(R.styleable.MaterialRippleLayout_rippleOverlay, DEFAULT_RIPPLE_OVERLAY);
        //        rippleHover = a.getBoolean(R.styleable.MaterialRippleLayout_rippleHover, DEFAULT_HOVER);
        //        rippleDuration = a.getInt(R.styleable.MaterialRippleLayout_rippleDuration, DEFAULT_DURATION);
        //        rippleAlpha = (int) (255 * a.getFloat(R.styleable.MaterialRippleLayout_rippleAlpha, DEFAULT_ALPHA));
        //        rippleDelayClick = a.getBoolean(R.styleable.MaterialRippleLayout_rippleDelayClick, DEFAULT_DELAY_CLICK);
        //        rippleFadeDuration = a.getInteger(R.styleable.MaterialRippleLayout_rippleFadeDuration, DEFAULT_FADE_DURATION);
        //        rippleBackground = new ColorDrawable(a.getColor(R.styleable.MaterialRippleLayout_rippleBackground,
        //                DEFAULT_BACKGROUND));
        //        ripplePersistent = a.getBoolean(R.styleable.MaterialRippleLayout_ripplePersistent, DEFAULT_PERSISTENT);
        //        //rippleInAdapter = a.getBoolean(R.styleable.MaterialRippleLayout_rippleInAdapter, DEFAULT_SEARCH_ADAPTER);
        //
        //        mInterceptTouchEvent = a.getBoolean(R.styleable.MaterialRippleLayout_rippleInterceptTouchEvent,
        //                DEFAULT_INTERCEPT_TOUCH_EVENT);
        //        mSuperTouchEvent = a.getBoolean(R.styleable.MaterialRippleLayout_rippleSuperTouchEvent,
        //                DEFAULT_SUPER_TOUCH_EVENT);
        //        mAvoidTwiceTouch = a.getBoolean(R.styleable.MaterialRippleLayout_rippleAvoidTwiceTouch,
        //                DEFAULT_AVOID_TWICE_TOUCH);

        //        a.recycle();
        rippleBackground = new ColorDrawable(DEFAULT_BACKGROUND);
        rippleColor = DEFAULT_COLOR;
        rippleAlpha = (int) (255 * DEFAULT_ALPHA);
        rippleDelayClick = DEFAULT_DELAY_CLICK;
        mInterceptTouchEvent = DEFAULT_INTERCEPT_TOUCH_EVENT;
        mSuperTouchEvent = DEFAULT_SUPER_TOUCH_EVENT;
        rippleHover = DEFAULT_HOVER;

        paint.setColor(rippleColor);
        paint.setAlpha(rippleAlpha);
    }

    public boolean onTouchEvent(MotionEvent event, boolean superOnTouchEvent, boolean isEnable) {

        if (!isEnable) {
            return superOnTouchEvent;
        }

        boolean isEventInBounds = bounds.contains((int) event.getX(), (int) event.getY());

        if (isEventInBounds) {
            previousCoords.set(currentCoords.x, currentCoords.y);
            currentCoords.set((int) event.getX(), (int) event.getY());
        }
        int action = event.getActionMasked();
        switch (action) {
        case MotionEvent.ACTION_UP:

            if (prepressed) {
                targetView.setPressed(true);
                targetView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        targetView.setPressed(false);
                    }
                }, ViewConfiguration.getPressedStateDuration());
            }

            if (isEventInBounds) {
                startRipple(new PerformClickEvent());
            }
            cancelPressedEvent();
            break;
        case MotionEvent.ACTION_DOWN:
            setPositionInAdapter();
            eventCancelled = false;
            //                 if (isInScrollingContainer()) {
            //                     cancelPressedEvent();
            //                     prepressed = true;
            //                     pendingPressEvent = new PressedEvent(event);
            //                     postDelayed(pendingPressEvent, ViewConfiguration.getTapTimeout());
            //                 } else {
            targetView.setPressed(true);
            if (rippleHover) {
                startHover();
            }
            //                 }
            break;
        case MotionEvent.ACTION_CANCEL:
            if (rippleInAdapter) {
                // dont use current coords in adapter since they tend to jump drastically on scroll
                currentCoords.set(previousCoords.x, previousCoords.y);
                previousCoords = new Point();
            }
            if (rippleHover) {
                if (!prepressed) {
                    startRipple(null);
                }
            } else {
                targetView.setPressed(false);
            }
            cancelPressedEvent();
            break;
        case MotionEvent.ACTION_MOVE:
            if (rippleHover) {
                if (isEventInBounds && !eventCancelled) {
                    targetView.invalidate();
                } else if (!isEventInBounds) {
                    startRipple(null);
                }
            }

            if (!isEventInBounds) {
                cancelPressedEvent();
                if (hoverAnimator != null) {
                    hoverAnimator.cancel();
                }
                eventCancelled = true;
            }
            break;
        }
        return true;
    }

    private void cancelPressedEvent() {
        if (pendingPressEvent != null) {
            targetView.removeCallbacks(pendingPressEvent);
            prepressed = false;
        }
    }

    private void startHover() {
        if (eventCancelled) {
            return;
        }

        if (hoverAnimator != null) {
            hoverAnimator.cancel();
        }
        final float radius = (float) (Math.sqrt(Math.pow(targetView.getWidth(), 2)
                + Math.pow(targetView.getHeight(), 2)) * 1.2f);
        hoverAnimator = ObjectAnimator.ofFloat(this, radiusProperty, rippleDiameter, radius)
                .setDuration(HOVER_DURATION);
        hoverAnimator.setInterpolator(new LinearInterpolator());
        hoverAnimator.start();
    }

    private void startRipple(final Runnable animationEndRunnable) {
        if (eventCancelled) {
            return;
        }

        float endRadius = getEndRadius();

        cancelAnimations();

        rippleAnimator = new AnimatorSet();
        rippleAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (!ripplePersistent) {
                    setRadius(0);
                    setRippleAlpha(rippleAlpha);
                }
                if (animationEndRunnable != null && rippleDelayClick) {
                    animationEndRunnable.run();
                }
                targetView.setPressed(false);
            }
        });

        ObjectAnimator ripple = ObjectAnimator.ofFloat(this, radiusProperty, radius, endRadius);
        ripple.setDuration(rippleDuration);
        ripple.setInterpolator(new DecelerateInterpolator());
        ObjectAnimator fade = ObjectAnimator.ofInt(this, circleAlphaProperty, rippleAlpha, 0);
        fade.setDuration(rippleFadeDuration);
        fade.setInterpolator(new AccelerateInterpolator());
        fade.setStartDelay(rippleDuration - rippleFadeDuration - FADE_EXTRA_DELAY);

        if (ripplePersistent) {
            rippleAnimator.play(ripple);
        } else if (getRadius() > endRadius) {
            fade.setStartDelay(0);
            rippleAnimator.play(fade);
        } else {
            rippleAnimator.playTogether(ripple, fade);
        }
        rippleAnimator.start();
    }

    private void cancelAnimations() {
        if (rippleAnimator != null) {
            rippleAnimator.cancel();
            rippleAnimator.removeAllListeners();
        }

        if (hoverAnimator != null) {
            hoverAnimator.cancel();
        }
    }

    private float getEndRadius() {
        final int width = targetView.getWidth();
        final int height = targetView.getHeight();

        final int halfWidth = width / 2;
        final int halfHeight = height / 2;

        final float radiusX = halfWidth > currentCoords.x ? width - currentCoords.x : currentCoords.x;
        final float radiusY = halfHeight > currentCoords.y ? height - currentCoords.y : currentCoords.y;

        return (float) Math.sqrt(Math.pow(radiusX, 2) + Math.pow(radiusY, 2)) * 1.2f;
    }

    @SuppressLint("NewApi")
    public boolean isInScrollingContainer() {
        ViewParent p = targetView.getParent();
        while (p != null && p instanceof ViewGroup) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                if (((ViewGroup) p).shouldDelayChildPressedState()) {
                    return true;
                }
            }/* else if (p instanceof ScrollView) {
                return true;
             }*/
            p = p.getParent();
        }
        return false;
    }

    private AdapterView findParentAdapterView() {
        if (parentAdapter != null) {
            return parentAdapter;
        }
        ViewParent current = targetView.getParent();
        while (true) {
            if (current instanceof AdapterView) {
                parentAdapter = (AdapterView) current;
                return parentAdapter;
            } else {
                try {
                    current = current.getParent();
                } catch (NullPointerException npe) {
                    throw new RuntimeException("Could not find a parent AdapterView");
                }
            }
        }
    }

    private void setPositionInAdapter() {
        if (rippleInAdapter) {
            positionInAdapter = findParentAdapterView().getPositionForView(targetView);
        }
    }

    private boolean adapterPositionChanged() {
        if (rippleInAdapter) {
            int newPosition = findParentAdapterView().getPositionForView(targetView);
            final boolean changed = newPosition != positionInAdapter;
            positionInAdapter = newPosition;
            if (changed) {
                cancelPressedEvent();
                cancelAnimations();
                targetView.setPressed(false);
                setRadius(0);
            }
            return changed;
        }
        return false;
    }

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        bounds.set(0, 0, w, h);
        rippleBackground.setBounds(bounds);
    }

    /*
     * Drawing
     */
    public void draw(Canvas canvas) {
        final boolean positionChanged = adapterPositionChanged();
        if (rippleOverlay) {
            if (!positionChanged) {
                rippleBackground.draw(canvas);
            }
            viewDraw(canvas);
            if (!positionChanged) {
                canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
            }
        } else {
            if (!positionChanged) {
                rippleBackground.draw(canvas);
                canvas.drawCircle(currentCoords.x, currentCoords.y, radius, paint);
            }
            viewDraw(canvas);
        }
    }

    private void viewDraw(Canvas canvas) {
        if (targetView instanceof RippleAble) {
            RippleAble viewDrawAble = (RippleAble) targetView;
            viewDrawAble.viewDraw(canvas);
        }

    }

    /*
     * Animations
     */
    private final Property<MaterialRippleHolder, Float> radiusProperty = new Property<MaterialRippleHolder, Float>(
            Float.class, "radius") {
        @Override
        public Float get(MaterialRippleHolder object) {
            return object.getRadius();
        }

        @Override
        public void set(MaterialRippleHolder object, Float value) {
            object.setRadius(value);
        }
    };

    private float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
        targetView.invalidate();
    }

    private final Property<MaterialRippleHolder, Integer> circleAlphaProperty = new Property<MaterialRippleHolder, Integer>(
            Integer.class, "rippleAlpha") {
        @Override
        public Integer get(MaterialRippleHolder object) {
            return object.getRippleAlpha();
        }

        @Override
        public void set(MaterialRippleHolder object, Integer value) {
            object.setRippleAlpha(value);
        }
    };

    public int getRippleAlpha() {
        return paint.getAlpha();
    }

    public void setRippleAlpha(Integer rippleAlpha) {
        paint.setAlpha(rippleAlpha);
        targetView.invalidate();
    }

    /*
    * Accessor
     */
    public void setRippleColor(int rippleColor) {
        this.rippleColor = rippleColor;
        paint.setColor(rippleColor);
        paint.setAlpha(rippleAlpha);
        targetView.invalidate();
    }

    public void setRippleOverlay(boolean rippleOverlay) {
        this.rippleOverlay = rippleOverlay;
    }

    public void setRippleDiameter(int rippleDiameter) {
        this.rippleDiameter = rippleDiameter;
    }

    public void setRippleDuration(int rippleDuration) {
        this.rippleDuration = rippleDuration;
    }

    public void setRippleBackground(int color) {
        rippleBackground = new ColorDrawable(color);
        rippleBackground.setBounds(bounds);
        targetView.invalidate();
    }

    public void setRippleHover(boolean rippleHover) {
        this.rippleHover = rippleHover;
    }

    public void setInterceptTouchEvent(boolean interceptTouchEvent) {
        mInterceptTouchEvent = interceptTouchEvent;
    }

    public void setSuperTouchEvent(boolean superTouchEvent) {
        mSuperTouchEvent = superTouchEvent;
    }

    public void setAvoidTwiceTouch(boolean avoidTwiceTouch) {
        mAvoidTwiceTouch = avoidTwiceTouch;
    }

    public void setRippleDelayClick(boolean rippleDelayClick) {
        this.rippleDelayClick = rippleDelayClick;
    }

    public void setRippleFadeDuration(int rippleFadeDuration) {
        this.rippleFadeDuration = rippleFadeDuration;
    }

    public void setRipplePersistent(boolean ripplePersistent) {
        this.ripplePersistent = ripplePersistent;
    }

    public void setRippleInAdapter(boolean rippleInAdapter) {
        this.rippleInAdapter = rippleInAdapter;
    }

    public void setDefaultRippleAlpha(int alpha) {
        rippleAlpha = alpha;
        paint.setAlpha(alpha);
        targetView.invalidate();
    }

    private final class PressedEvent implements Runnable {

        private final MotionEvent event;

        public PressedEvent(MotionEvent event) {
            this.event = event;
        }

        @Override
        public void run() {
            prepressed = false;
            targetView.setPressed(true);
            if (rippleHover) {
                startHover();
            }
        }
    }

    static float dpToPx(Resources resources, float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics());
    }

    /*
     * Builder
     */

    public static class RippleBuilder {

        private final Context context;
        private final View child;

        private int rippleColor = DEFAULT_COLOR;
        private boolean rippleOverlay = DEFAULT_RIPPLE_OVERLAY;
        private boolean rippleHover = DEFAULT_HOVER;
        private float rippleDiameter = DEFAULT_DIAMETER_DP;
        private int rippleDuration = DEFAULT_DURATION;
        private float rippleAlpha = DEFAULT_ALPHA;
        private boolean rippleDelayClick = DEFAULT_DELAY_CLICK;
        private int rippleFadeDuration = DEFAULT_FADE_DURATION;
        private boolean ripplePersistent = DEFAULT_PERSISTENT;
        private int rippleBackground = DEFAULT_BACKGROUND;
        private final boolean rippleSearchAdapter = DEFAULT_SEARCH_ADAPTER;
        private boolean mInterceptTouchEvent = DEFAULT_INTERCEPT_TOUCH_EVENT;
        private boolean mSuperTouchEvent = DEFAULT_SUPER_TOUCH_EVENT;

        public RippleBuilder(View child) {
            this.child = child;
            context = child.getContext();
        }

        public RippleBuilder rippleColor(int color) {
            rippleColor = color;
            return this;
        }

        public RippleBuilder rippleOverlay(boolean overlay) {
            rippleOverlay = overlay;
            return this;
        }

        public RippleBuilder rippleHover(boolean hover) {
            rippleHover = hover;
            return this;
        }

        public RippleBuilder rippleInterceptTouchEvent(boolean intercept) {
            mInterceptTouchEvent = intercept;
            return this;
        }

        public RippleBuilder rippleSuperTouchEvent(boolean superTouchEvent) {
            mSuperTouchEvent = superTouchEvent;
            return this;
        }

        public RippleBuilder rippleDiameterDp(int diameterDp) {
            rippleDiameter = diameterDp;
            return this;
        }

        public RippleBuilder rippleDuration(int duration) {
            rippleDuration = duration;
            return this;
        }

        public RippleBuilder rippleAlpha(float alpha) {
            rippleAlpha = 255 * alpha;
            return this;
        }

        public RippleBuilder rippleDelayClick(boolean delayClick) {
            rippleDelayClick = delayClick;
            return this;
        }

        public RippleBuilder rippleFadeDuration(int fadeDuration) {
            rippleFadeDuration = fadeDuration;
            return this;
        }

        public RippleBuilder ripplePersistent(boolean persistent) {
            ripplePersistent = persistent;
            return this;
        }

        public RippleBuilder rippleBackground(int color) {
            rippleBackground = color;
            return this;
        }

        public RippleBuilder rippleInAdapter(boolean inAdapter) {
            rippleInAdapter(inAdapter);
            return this;
        }

    }

    /*
     * Helper
     */
    private class PerformClickEvent implements Runnable {

        @Override
        public void run() {
            if (targetView.getParent() instanceof AdapterView) {
                clickAdapterView((AdapterView) targetView.getParent());
            }
            if (onClickListener != null) {
                onClickListener.onClick(targetView);
            }
        }

        private void clickAdapterView(AdapterView parent) {
            final int position = parent.getPositionForView(targetView);
            final long itemId = parent.getAdapter() != null ? parent.getAdapter().getItemId(position) : 0;
            if (position != AdapterView.INVALID_POSITION) {
                parent.performItemClick(targetView, position, itemId);
            }
        }
    }

    public void setTargetClickListener(OnClickListener l) {
        this.onClickListener = l;
    }

}
